package com.snail.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.snail.common.DeleteRequest;
import com.snail.config.SrtGenerator;
import com.snail.constants.RabbitMqConstants;
import com.snail.exception.BusinessException;
import com.snail.exception.ErrorCode;
import com.snail.mapper.BizMediaFileMapper;
import com.snail.model.dto.UploadFileParamsDto;
import com.snail.model.dto.UploadFileResultDto;
import com.snail.model.pojo.BizMediaDoc;
import com.snail.model.pojo.BizMediaFile;
import com.snail.model.vo.VideoPageListVo;
import com.snail.response.BaseResponse;
import com.snail.response.PageResponse;
import com.snail.response.ResultUtils;
import com.snail.service.BizMediaDocService;
import com.snail.service.BizMediaFileService;
import com.snail.utils.AudioProcessingUtil;
import com.snail.utils.FfmpegCmd;
import com.snail.utils.Subtitle;
import com.snail.utils.TranscriptionServiceUtil;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Monica
 * @description 针对表【biz_media_file(文件储存表)】的数据库操作Service实现
 * @date 2025-07-16 10:22:25
 */
@Service
@Slf4j
public class BizMediaFileServiceImpl extends ServiceImpl<BizMediaFileMapper, BizMediaFile>
        implements BizMediaFileService {

    @Autowired
    private MinioClient minioClient;
    @Autowired
    private BizMediaFileMapper bizMediaFileMapper;
    @Autowired
    @Lazy
    private BizMediaFileService currentProxy;

    @Resource
    private BizMediaDocService bizMediaDocService;

    @Autowired
    private AudioProcessingUtil audioProcessingUtil;

    @Autowired
    private TranscriptionServiceUtil transcriptionServiceUtil;

    @Autowired
    private SrtGenerator srtGenerator;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 临时目录
    public static final String TEMP_DIR = System.getProperty("user.dir") + "/temp";

    //图片文件桶
    @Value("${minio.bucket.pic}")
    private String bucketPic;
    @Value("${minio.bucket.video}")
    private String bucketVideo;

    // minIo endpoint
    @Value("${minio.endpoint}")
    public String minioEndpoint;

    @Override
    public BaseResponse<UploadFileResultDto> uploadFile(UploadFileParamsDto uploadFileParamsDto, String absolutePath) {
        File file = new File(absolutePath);
        if (!file.exists()) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        //文件名称
        String filename = uploadFileParamsDto.getFileName();
        //文件扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        //文件mimeType
        String mimeType = getMimeType(extension);
        //文件的md5值
        String fileMd5 = getFileMd5(file);
        //文件的默认目录
        String defaultFolderPath = getDefaultFolderPath();
        //存储到minio中的对象名(带目录)
        String objectName = defaultFolderPath + fileMd5 + extension;
        //将文件上传到minio
        boolean b = addMediaFilesToMinio(absolutePath, mimeType, bucketPic, objectName);
        //文件大小
        uploadFileParamsDto.setFileSize(file.length());
        //将文件信息存储到数据库
        BizMediaFile mediaFiles = currentProxy.addMediaFilesToDb(fileMd5, uploadFileParamsDto, bucketPic, objectName);
        //准备返回数据
        UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
        BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
        return ResultUtils.success(uploadFileResultDto);
    }


    @Override
    public BaseResponse<Boolean> deleteFile(String fileMd5) {
        //查询文件信息
        BizMediaFile mediaFile = currentProxy.getMediaFileByFileMd5(fileMd5);
        if (mediaFile == null) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        String objectName = mediaFile.getFilePath();
        // 从minio中删除
        RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(mediaFile.getBucket())
                .object(objectName)
                .build();
        try {
            minioClient.removeObject(args);
        } catch (Exception e) {
            log.info("删除文件,文件不存在,文件md5:{}", fileMd5, e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        //删除文件
        int flag = bizMediaFileMapper.deleteById(mediaFile.getId());
        if (flag == 0) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        return ResultUtils.success(true);
    }

    /**
     * @param fileMd5 文件md5值
     * @return BaseResponse<Boolean>
     * @description 校验文件是否存在
     */
    @Override
    public BaseResponse<Boolean> checkFile(String fileMd5) {
        //查询文件信息
        BizMediaFile mediaFile = currentProxy.getMediaFileByFileMd5(fileMd5);
        if (mediaFile != null) {
            //桶
            String bucket = mediaFile.getBucket();
            //存储目录
            String filePath = mediaFile.getFilePath();
            //文件流
            InputStream stream = null;
            try {
                stream = minioClient.getObject(
                        GetObjectArgs.builder()
                                .bucket(bucket)
                                .object(filePath)
                                .build());

                if (stream != null) {
                    //文件已存在
                    return ResultUtils.success(true);
                }
            } catch (Exception e) {
                log.info("校验文件是否存在,文件不存在,文件md5:{}", fileMd5, e);
            }
        }
        //文件不存在
        return ResultUtils.success(false);
    }

    /**
     * @param fileMd5 文件md5值
     * @param chunk   分块号
     * @return BaseResponse<Boolean>
     * @description 校验分块文件是否存在
     */
    @Override
    public BaseResponse<Boolean> checkChunk(String fileMd5, int chunk) {
        //得到分块文件目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunk;

        //文件流
        InputStream fileInputStream = null;
        try {
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketVideo)
                            .object(chunkFilePath)
                            .build());

            if (fileInputStream != null) {
                //分块已存在
                return ResultUtils.success(true);
            }
        } catch (Exception e) {
            log.info("校验分块文件是否存在,文件不存在,文件md5:{},分块号:{}", fileMd5, chunk, e);
        }
        //分块未存在
        return ResultUtils.success(false);
    }

    /**
     * 文件格式：/2025/11/11/video/md5值/1（分块编号）
     *
     * @param fileMd5            文件md5值
     * @param chunk              分块号
     * @param localChunkFilePath 分块文件本地路径
     * @return BaseResponse<Boolean>
     * @description 上传分块文件
     */
    @Override
    public BaseResponse uploadChunk(String fileMd5, int chunk, String localChunkFilePath) {

        //得到分块文件的目录路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunk;
        //mimeType
        String mimeType = getMimeType(null);
        //将文件存储至minIO
        boolean b = addMediaFilesToMinio(localChunkFilePath, mimeType, bucketVideo, chunkFilePath);
        if (!b) {
            log.debug("上传分块文件失败:{}", chunkFilePath);
            return ResultUtils.error("上传分块失败");
        }
        log.debug("上传分块文件成功:{}", chunkFilePath);
        return ResultUtils.success(true);
    }

    @Override
    public BaseResponse mergeChunks(String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        //获取分块文件路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //组成将分块文件路径组成 List<ComposeSource>
        List<ComposeSource> sourceObjectList = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> ComposeSource.builder()
                        .bucket(bucketVideo)
                        .object(chunkFileFolderPath.concat(Integer.toString(i)))
                        .build())
                .collect(Collectors.toList());
        //=====合并=====
        //文件名称
        String fileName = uploadFileParamsDto.getFileName();
        //文件扩展名
        String extName = fileName.substring(fileName.lastIndexOf("."));
        //合并文件路径
        String mergeFilePath = getFilePathByMd5(fileMd5, extName);
        ObjectWriteResponse response = null;
        try {
            //合并文件
            response = minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucketVideo)
                            .object(mergeFilePath)
                            .sources(sourceObjectList)
                            .build());
            log.debug("合并文件成功:{}", mergeFilePath);
        } catch (Exception e) {
            log.debug("合并文件失败,fileMd5:{},异常:{}", fileMd5, e.getMessage(), e);
            return ResultUtils.error("合并文件失败。");
        }
//        String etag = response.etag();
//        if (!etag.equals(fileMd5)) {
//            return ResultUtils.error("合并文件失败。");
//        }
        // TODO 校验合并文件（在考虑下方式）
        // 验证md5是否和源文件的md5值一样
        // 下载合并之后的文件
        File minioFile = downloadFileFromMinIo(bucketVideo, mergeFilePath);
        if (minioFile == null) {
            log.debug("下载合并后文件失败,mergeFilePath:{}", mergeFilePath);
            return ResultUtils.error("下载合并后文件失败。");
        }
        // 计算md5值
        try (InputStream newFileInputStream = new FileInputStream(minioFile)) {
            //minio上文件的md5值
            String md5Hex = DigestUtil.md5Hex(newFileInputStream);
            //比较md5值，不一致则说明文件不完整
            if (!fileMd5.equals(md5Hex)) {
                return ResultUtils.error("文件合并校验失败，最终上传失败。");
            }
            //文件大小
            uploadFileParamsDto.setFileSize(minioFile.length());
        } catch (Exception e) {
            log.debug("校验文件失败,fileMd5:{},异常:{}", fileMd5, e.getMessage(), e);
            return ResultUtils.error("文件合并校验失败，最终上传失败。");
        } finally {
            if (minioFile != null) {
                minioFile.delete();
            }
        }
        //文件入库
        BizMediaFile bizMediaFile = currentProxy.addMediaFilesToDb(fileMd5, uploadFileParamsDto, bucketVideo, mergeFilePath);
        rabbitTemplate.convertAndSend(RabbitMqConstants.VIDEO_DIRECT_EXCHANGE, RabbitMqConstants.VIDEO_DIRECT_ROUTING_KEY, bizMediaFile);
        //=====清除分块文件=====
        clearChunkFiles(chunkFileFolderPath, chunkTotal, bucketVideo);
        return ResultUtils.success(true);
    }


    /**
     * @param localFilePath 文件地址
     * @param bucket        桶
     * @param objectName    对象名称
     * @return boolean
     * @description 将文件写入minIO
     */
    public boolean addMediaFilesToMinio(String localFilePath, String mimeType, String bucket, String objectName) {
        try {
            UploadObjectArgs objectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .filename(localFilePath)
                    .contentType(mimeType)
                    .build();

            minioClient.uploadObject(objectArgs);
            log.debug("上传文件到minio成功,bucket:{},objectName:{}", bucket, objectName);
            return true;
        } catch (Exception e) {
            log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}", bucket, objectName, e.getMessage(), e);
//            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"上传文件到minio出错");
        }
        return false;
    }

    /**
     * @param fileMd5             文件md5值
     * @param uploadFileParamsDto 上传文件的信息
     * @param bucket              桶
     * @param objectName          对象名称
     * @description 将文件信息添加到文件表
     */
    @Override
    @Transactional
    public BizMediaFile addMediaFilesToDb(String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName) {
        BizMediaFile mediaFile = currentProxy.getMediaFileByFileMd5(fileMd5);
        if (mediaFile == null) {
            mediaFile = new BizMediaFile();
            //拷贝基本信息
            BeanUtils.copyProperties(uploadFileParamsDto, mediaFile);
            mediaFile.setFileMd5(fileMd5);
            mediaFile.setFilePath("/" + bucket + "/" + objectName);
            mediaFile.setBucket(bucket);
            mediaFile.setFilePath(objectName);
            // TODO 获取上传人的信息
            //保存文件信息到文件表
            int insert = bizMediaFileMapper.insert(mediaFile);
            if (insert < 0) {
                log.error("保存文件信息到数据库失败,{}", mediaFile.toString());

            }
            log.debug("保存文件信息到数据库成功,{}", mediaFile.toString());

        }
        return mediaFile;

    }


    /**
     * 获取默认的文件存储目录路径，格式为：年/月/日/
     * 例如：2023/10/25/
     *
     * @return 返回格式化后的日期路径字符串，如 "2023/10/25/"
     */
    private String getDefaultFolderPath() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date()).replace("-", "/") + "/";
    }

    /**
     * 计算文件的 MD5 哈希值（用于文件唯一性校验或指纹识别）
     *
     * @param file 要计算 MD5 的文件对象
     * @return 返回文件的 MD5 哈希值（32位小写字符串），如果计算失败则返回 null
     * @throws RuntimeException 如果文件读取失败或计算 MD5 时发生异常
     */
    private String getFileMd5(File file) {
        try {
            return DigestUtil.md5Hex(file);
        } catch (Exception e) {
            log.error("计算文件 MD5 哈希值失败,文件路径:{}", file.getAbsolutePath(), e);
            return null;
        }
    }

    /**
     * 根据文件扩展名获取对应的 MIME 类型（如 "txt" → "text/plain"）
     * 如果无法识别扩展名，默认返回通用的二进制流类型（application/octet-stream）
     *
     * @param extension 文件扩展名（不带点，如 "png"、"txt"），可为 null 或空字符串
     */
    private String getMimeType(String extension) {
        if (StrUtil.isBlank(extension)) {
            return MediaType.APPLICATION_OCTET_STREAM_VALUE;
        }
        String mimeType = FileUtil.getMimeType("file." + extension);
        return mimeType != null ? mimeType : MediaType.APPLICATION_OCTET_STREAM_VALUE;
    }

    @Override
    @Transactional
    public BizMediaFile getMediaFileByFileMd5(String fileMd5) {
        //从数据库查询文件
        LambdaQueryWrapper<BizMediaFile> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BizMediaFile::getFileMd5, fileMd5);
        return bizMediaFileMapper.selectOne(queryWrapper);
    }

    //得到分块文件的目录
    private String getChunkFileFolderPath(String fileMd5) {
        return fileMd5.charAt(0) + "/" + fileMd5.charAt(1) + "/" + fileMd5 + "/" + "chunk" + "/";
    }


    /**
     * 得到合并后的文件的地址
     *
     * @param fileMd5 文件id即md5值
     * @param fileExt 文件扩展名
     * @return
     */
    private String getFilePathByMd5(String fileMd5, String fileExt) {
        return fileMd5.charAt(0) + "/" + fileMd5.charAt(1) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }

    /**
     * 从minio下载文件
     *
     * @param bucketVideo   桶
     * @param mergeFilePath 合并后的文件路径
     * @return
     */
    private File downloadFileFromMinIo(String bucketVideo, String mergeFilePath) {
        //临时文件
        File minioFile = null;
        FileOutputStream outputStream = null;
        try {
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketVideo)
                    .object(mergeFilePath)
                    .build());
            //创建临时文件
            minioFile = File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream, outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 清除分块文件
     *
     * @param chunkFileFolderPath 分块文件路径
     * @param chunkTotal          分块文件总数
     */
    private void clearChunkFiles(String chunkFileFolderPath, int chunkTotal, String bucket) {

        try {
            List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                    .limit(chunkTotal)
                    .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
                    .collect(Collectors.toList());

            RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucket).objects(deleteObjects).build();
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
            results.forEach(r -> {
                DeleteError deleteError = null;
                try {
                    deleteError = r.get();
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("清楚分块文件失败,objectName:", e);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            log.error("清楚分块文件失败,chunkFileFolderPath:{}", chunkFileFolderPath, e);
        }
    }


    /**
     * 使用 FFmpeg 将视频转换为音频（WAV 格式）
     */
    @Override
    public void convertVideoToAudio(File videoFile, File audioFile) throws IOException {
        String cmd = "-y -i {input} -vn -acodec pcm_s16le -ar 16000 -ac 1 {output}";
        cmd = cmd.replace("{input}", videoFile.getAbsolutePath())
                .replace("{output}", audioFile.getAbsolutePath());

        FfmpegCmd ffmpegCmd = new FfmpegCmd();
        ffmpegCmd.execute(false, true, cmd);

        int exitCode = ffmpegCmd.getProcessExitCode();
        ffmpegCmd.close();

        if (exitCode != 0 || !audioFile.exists()) {
            throw new RuntimeException("音频提取失败，退出码: " + exitCode);
        }
    }

    /**
     * 提交音频文件进行语音识别，返回识别结果字符串
     */
    @Override
    public String transcribeAudioToString(File audioFile) throws IOException, ExecutionException, InterruptedException, TimeoutException {
        // 检查audioFile是否存在
        if (!audioFile.exists()) {
            throw new RuntimeException("音频文件不存在");
        }
//        // 使用线程池异步执行语音识别任务
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
//        Future<String> future = executorService.submit(() -> {
//            CountDownLatch countDownLatch = new CountDownLatch(1);
//            final String[] result = {null};
//            RealtimeTranslateTask task = new RealtimeTranslateTask(Path.of(audioFile.getPath()), (s) -> {
//                result[0] = s;
//                countDownLatch.countDown();
//            });
//            task.run();
//            try {
//                // 等待识别完成
//                countDownLatch.await();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            return result[0];
//        });
//
//        // 等待识别结果（可优化为回调或 WebSocket）
//        String result = future.get(5, TimeUnit.MINUTES);
//        executorService.shutdown();
//        if (result == null || result.trim().isEmpty()) {
//            throw new RuntimeException("语音识别结果为空");
//        }
//        return result;

//        ________________________________________________________
        // 1. 指定本地音频文件路径
        File sourceFile = audioFile;


        if (!sourceFile.exists()) {
            throw new IOException("音频文件不存在: " + audioFile);
        }

        // 2. 创建临时工作副本（避免修改原始文件）
        Path tempPath = Files.createTempFile("audio-", ".wav");
        File tempFile = tempPath.toFile();
        Files.copy(sourceFile.toPath(), tempPath, StandardCopyOption.REPLACE_EXISTING);

        try {
            // 3. 获取音频总时长（毫秒）
            long duration = audioProcessingUtil.getAudioDuration(tempFile);

            // 4. 计算分段参数（每15秒一段）
            final int segmentDurationSeconds = 60 * 3;
            final long segmentDurationMillis = segmentDurationSeconds * 1000L;
            int numSegments = (int) Math.ceil((double) duration / segmentDurationMillis);
//            System.out.println("需要分割成 " + numSegments + " 个片段");

            // 5. 创建线程池（优化线程数量）
            //threadCount 是根据 CPU 核心数和音频分段数量决定的，避免线程过多导致资源争用。
            int threadCount = Math.min(numSegments, Runtime.getRuntime().availableProcessors());//向上取整
            ExecutorService executor = Executors.newFixedThreadPool(threadCount);
            List<Future<Subtitle>> futures = new ArrayList<>();

            // 6. 并行处理每个音频片段
            for (int i = 0; i < numSegments; i++) {
                final int segmentIndex = i;
                futures.add(executor.submit(() -> {
                    long startTime = segmentIndex * segmentDurationMillis;
                    long endTime = Math.min(startTime + segmentDurationMillis, duration);


                    // 分割音频片段
                    File segmentFile = audioProcessingUtil.splitAudio(
                            tempFile, startTime, endTime);

                    try {
                        // 语音识别
                        String text = transcriptionServiceUtil.transcribeAudioFile(segmentFile);
                        return new Subtitle(startTime, endTime, text);
                    } finally {
                        // 确保删除临时片段文件
                        if (!segmentFile.delete()) {
                            log.info("警告: 无法删除临时文件: {}", segmentFile.getPath());
                        }
                    }
                }));
            }

            // 7. 收集并排序识别结果
            List<Subtitle> subtitles = new ArrayList<>();
            for (Future<Subtitle> future : futures) {
                subtitles.add(future.get());
            }
            subtitles.sort(Comparator.comparingLong(Subtitle::getStartTimeMillis));

            // 8. 关闭线程池
            executor.shutdown();
            if (!executor.awaitTermination(2, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }

            // 9. 生成SRT字幕
            return srtGenerator.generateSRT(subtitles);

        } finally {
            // 10. 清理临时文件
            if (tempFile.exists() && !tempFile.delete()) {
                System.err.println("警告: 无法删除主临时文件: " + tempFile.getPath());
            }
        }
    }

    @Override
    public Path downloadVideoFile(String videoUrl) throws IOException, URISyntaxException, InterruptedException {
        // 创建临时工作目录
        Path tempDirPath = Paths.get(TEMP_DIR);
        if (!Files.exists(tempDirPath)) {
            Files.createDirectories(tempDirPath);
        }
        // 下载远程视频到本地临时文件
        HttpClient httpClient = HttpClient.newHttpClient();
        // 1. 视频转音频
        Path tempVideoFile = Files.createTempFile(tempDirPath, "video_", ".mp4");
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI(videoUrl))
                .build();
        HttpResponse<Path> httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(tempVideoFile));
        if (httpResponse.statusCode() != 200) {
            throw new IOException("下载视频失败，状态码: " + httpResponse.statusCode());
        }
        return tempVideoFile;
    }

    @Override
    public PageResponse<BizMediaFile> getMediaFileList(VideoPageListVo videoPageListVo) {
        Page<BizMediaFile> mediaFilePage = new Page<>(videoPageListVo.getCurrent(), videoPageListVo.getPageSize());
        LambdaQueryWrapper<BizMediaFile> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.hasLength(videoPageListVo.getFileName())) {
            queryWrapper.like(BizMediaFile::getFileName, videoPageListVo.getFileName());
        }
        queryWrapper.eq(BizMediaFile::getFileType, 1);
        Page<BizMediaFile> videoList = bizMediaFileMapper.selectPage(mediaFilePage, queryWrapper);

        return ResultUtils.pageSuccess(videoList);
    }

    @Override
    @Transactional
    public BaseResponse<Boolean> deleteVideoById(DeleteRequest deleteRequest) {
        BizMediaFile bizMediaFile = bizMediaFileMapper.selectById(deleteRequest.getId());
        if (bizMediaFile == null) {
            return null;
        }
        return currentProxy.deleteFile(bizMediaFile.getFileMd5());
    }

    @Override
    public BaseResponse<BizMediaFile> getMediaFileById(Integer id) {
        BizMediaFile bizMediaFile = bizMediaFileMapper.selectById(id);
        return ResultUtils.success(bizMediaFile);
    }

    @Override
    public void generateSubtitle() throws IOException {
        // 获取所有视频文件中subtitles_status为0的记录
        LambdaQueryWrapper<BizMediaFile> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BizMediaFile::getFileType, 1);
        queryWrapper.eq(BizMediaFile::getSubtitleId, 0);
        List<BizMediaFile> videoList = bizMediaFileMapper.selectList(queryWrapper);
        // 遍历记录，为每个记录生成字幕存入字幕表中
        try {
            for (BizMediaFile video : videoList) {
                Integer videoId = video.getId();
                String videoUrl = minioEndpoint + "/video/" + video.getFilePath();
                System.out.println(videoUrl);

                log.info("视频开始下载---------");
                Path tempVideoFile = this.downloadVideoFile(videoUrl);
                File videoFile = tempVideoFile.toFile();
                log.info("视频下载完成" + videoFile.getPath());

                // 定义输出音频文件路径
                File audioFile = new File(Paths.get(TEMP_DIR).toAbsolutePath() + "/audio_" + UUID.randomUUID() + ".wav");
                log.info("音频开始下载" + audioFile.getPath());
                this.convertVideoToAudio(videoFile, audioFile);
                log.info("音频下载完成" + audioFile.getPath());
                // 2. 音频转文字
                String subtitleText = this.transcribeAudioToString(audioFile);
//                    System.out.println("-------------" + subtitleText);
                // 3. 创建字幕记录
                BizMediaDoc bizMediaDoc = new BizMediaDoc();
                bizMediaDoc.setContent(subtitleText);
                bizMediaDoc.setVideoId(videoId);
                // 手动赋值更新文档记录
                bizMediaDocService.saveWithoutAutoFill(bizMediaDoc);
                // 更新视频记录
                video.setSubtitleId(bizMediaDoc.getId());
                this.updateByIdWithoutAutoFill(video);
                log.info("---------字幕记录创建成功");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 删除临时文件
            Files.list(Paths.get(this.TEMP_DIR))
                    .filter(Files::isRegularFile)
                    .forEach(path -> {
                        try {
                            Files.delete(path);
                        } catch (IOException e) {
                            System.err.println("删除失败: " + path);
                        }
                    });
        }
    }

    @Override
    public Integer getSubtitleId(Integer videoId) {
        LambdaQueryWrapper<BizMediaDoc> queryWrapper = new LambdaQueryWrapper<>();
        BizMediaFile video = this.getById(videoId);
        if (video != null && video.getFileType() == 1) {
            return video.getSubtitleId();
        }
        return 0;
    }

    @Override
    public void updateByIdWithoutAutoFill(BizMediaFile video) {
        video.setUpdateTime(LocalDateTime.now());
        video.setUpdateBy(0L);
        baseMapper.updateById(video);
    }
}




